home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1994 / MacHack 1994.toast / MacHack™ 1987-1994 / MacHack™ '87 / Source ƒ.sea / Source ƒ / emacs source ƒ / ISEARCH.C < prev    next >
Encoding:
C/C++ Source or Header  |  1992-06-28  |  21.1 KB  |  547 lines  |  [TEXT/MARC]

  1. /*
  2.  * The functions in this file implement commands that perform incremental
  3.  * searches in the forward and backward directions.  This "ISearch" command
  4.  * is intended to emulate the same command from the original EMACS 
  5.  * implementation (ITS).  Contains references to routines internal to
  6.  * SEARCH.C.
  7.  *
  8.  * REVISION HISTORY:
  9.  *
  10.  *    D. R. Banks 9-May-86
  11.  *    - added ITS EMACSlike ISearch
  12.  */
  13.  
  14. #include        <stdio.h>
  15. #include    "estruct.h"
  16. #include        "edef.h"
  17.  
  18. /*    string search input parameters    */
  19.  
  20. #define    PTBEG    1    /* leave the point at the beginning on search */
  21. #define    PTEND    2    /* leave the point at the end on search */
  22.  
  23. #define    CMDBUFLEN 256    /* Length of our command buffer */
  24.  
  25. extern int forscan();            /* Handy search routine */
  26. extern int eq();            /* Compare chars, match case */
  27.  
  28. /* A couple of "own" variables for re-eat */
  29.  
  30. int    (*saved_get_char)();        /* Get character routine */
  31. int    eaten_char = -1;        /* Re-eaten char */
  32.  
  33. /* A couple more "own" variables for the command string */
  34.  
  35. int    cmd_buff[CMDBUFLEN];        /* Save the command args here */
  36. int    cmd_offset;            /* Current offset into command buff */
  37. int    cmd_reexecute = -1;        /* > 0 if re-executing command */
  38.  
  39. /* Some character constants within ISearch */
  40.  
  41. #define    IS_ABORT    0x07    /* Abort the isearch */
  42. #define IS_BACKSP    0x08    /* Delete previous char */
  43. #define    IS_TAB        0x09    /* Tab character (allowed search char) */
  44. #define IS_NEWLINE    0x0D    /* New line from keyboard (Carriage return) */
  45. #define    IS_QUOTE    0x11    /* Quote next character */
  46. #define IS_REVERSE    0x12    /* Search backward */
  47. #define    IS_FORWARD    0x13    /* Search forward */
  48. #define    IS_VMSQUOTE    0x16    /* VMS quote character */
  49. #define    IS_VMSFORW    0x18    /* Search forward for VMS */
  50. #define    IS_QUIT        0x1B    /* Exit the search */
  51. #define    IS_RUBOUT    0x7F    /* Delete previous character */
  52. /* IS_QUIT is no longer used, the variable metac is used instead */
  53.  
  54. /*
  55.  * Subroutine to do incremental reverse search.  It actually uses the
  56.  * same code as the normal incremental search, as both can go both ways.
  57.  */
  58.  
  59. int risearch(f, n)
  60. {
  61.     LINE *curline;            /* Current line on entry          */
  62.     int  curoff;            /* Current offset on entry          */
  63.  
  64.     /* remember the initial . on entry: */
  65.  
  66.     curline = curwp->w_dotp;        /* Save the current line pointer      */
  67.     curoff  = curwp->w_doto;        /* Save the current offset          */
  68.  
  69.     /* Make sure the search doesn't match where we already are:              */
  70.  
  71.     backchar(TRUE, 1);            /* Back up a character              */
  72.  
  73.     if (!(isearch(f, -n)))        /* Call ISearch backwards          */
  74.     {                    /* If error in search:              */
  75.     curwp->w_dotp = curline;    /* Reset the line pointer          */
  76.     curwp->w_doto = curoff;        /*  and the offset to original value  */
  77.     curwp->w_flag |= WFMOVE;    /* Say we've moved              */
  78.     update(FALSE);            /* And force an update              */
  79.     mlwrite ("[search failed]");    /* Say we died                  */
  80.     } else mlerase ();            /* If happy, just erase the cmd line  */
  81. }
  82.  
  83. /* Again, but for the forward direction */
  84.  
  85. int fisearch(f, n)
  86. {
  87.     LINE *curline;            /* Current line on entry          */
  88.     int  curoff;            /* Current offset on entry          */
  89.  
  90.     /* remember the initial . on entry: */
  91.  
  92.     curline = curwp->w_dotp;        /* Save the current line pointer      */
  93.     curoff  = curwp->w_doto;        /* Save the current offset          */
  94.  
  95.     /* do the search */
  96.  
  97.     if (!(isearch(f, n)))        /* Call ISearch forwards          */
  98.     {                    /* If error in search:              */
  99.     curwp->w_dotp = curline;    /* Reset the line pointer          */
  100.     curwp->w_doto = curoff;        /*  and the offset to original value  */
  101.     curwp->w_flag |= WFMOVE;    /* Say we've moved              */
  102.     update(FALSE);            /* And force an update              */
  103.     mlwrite ("[search failed]");    /* Say we died                  */
  104.     } else mlerase ();            /* If happy, just erase the cmd line  */
  105. }
  106.  
  107. /*
  108.  * Subroutine to do an incremental search.  In general, this works similarly
  109.  * to the older micro-emacs search function, except that the search happens
  110.  * as each character is typed, with the screen and cursor updated with each
  111.  * new search character.
  112.  *
  113.  * While searching forward, each successive character will leave the cursor
  114.  * at the end of the entire matched string.  Typing a Control-S or Control-X
  115.  * will cause the next occurrence of the string to be searched for (where the
  116.  * next occurrence does NOT overlap the current occurrence).  A Control-R will
  117.  * change to a backwards search, META will terminate the search and Control-G
  118.  * will abort the search.  Rubout will back up to the previous match of the
  119.  * string, or if the starting point is reached first, it will delete the
  120.  * last character from the search string.
  121.  *
  122.  * While searching backward, each successive character will leave the cursor
  123.  * at the beginning of the matched string.  Typing a Control-R will search
  124.  * backward for the next occurrence of the string.  Control-S or Control-X
  125.  * will revert the search to the forward direction.  In general, the reverse
  126.  * incremental search is just like the forward incremental search inverted.
  127.  *
  128.  * In all cases, if the search fails, the user will be feeped, and the search
  129.  * will stall until the pattern string is edited back into something that
  130.  * exists (or until the search is aborted).
  131.  */
  132.  
  133. isearch(f, n)
  134. {
  135.     int            status;        /* Search status */
  136.     int            col;        /* prompt column */
  137.     register int    cpos;        /* character number in search string  */
  138.     register int    c;        /* current input character */
  139.     register int    expc;        /* function expanded input char          */
  140.     char        pat_save[NPAT];    /* Saved copy of the old pattern str  */
  141.     LINE        *curline;    /* Current line on entry          */
  142.     int            curoff;        /* Current offset on entry          */
  143.     int            init_direction;    /* The initial search direction          */
  144.  
  145.     /* Initialize starting conditions */
  146.  
  147.     cmd_reexecute = -1;        /* We're not re-executing (yet?)      */
  148.     cmd_offset = 0;            /* Start at the beginning of the buff */
  149.     cmd_buff[0] = '\0';        /* Init the command buffer          */
  150.     strncpy (pat_save, pat, NPAT);    /* Save the old pattern string          */
  151.     curline = curwp->w_dotp;        /* Save the current line pointer      */
  152.     curoff  = curwp->w_doto;        /* Save the current offset          */
  153.     init_direction = n;            /* Save the initial search direction  */
  154.  
  155.     /* This is a good place to start a re-execution: */
  156.  
  157. start_over:
  158.  
  159.     /* ask the user for the text of a pattern */
  160.     col = promptpattern("ISearch: ");        /* Prompt, remember the col   */
  161.  
  162.     cpos = 0;                    /* Start afresh              */
  163.     status = TRUE;                /* Assume everything's cool   */
  164.  
  165.     /*
  166.        Get the first character in the pattern.  If we get an initial Control-S
  167.        or Control-R, re-use the old search string and find the first occurrence
  168.      */
  169.  
  170.     c = ectoc(expc = get_char());        /* Get the first character    */
  171.     if ((c == IS_FORWARD) ||
  172.         (c == IS_REVERSE) ||
  173.         (c == IS_VMSFORW))            /* Reuse old search string?   */
  174.     {
  175.         for (cpos = 0; pat[cpos] != 0; cpos++)    /* Yup, find the length          */
  176.             col = echochar(pat[cpos],col);    /*  and re-echo the string    */
  177.     if (c == IS_REVERSE) {            /* forward search?          */
  178.         n = -1;                /* No, search in reverse      */
  179.         backchar (TRUE, 1);            /* Be defensive about EOB     */
  180.     } else
  181.         n = 1;                /* Yes, search forward          */
  182.     status = scanmore(pat,n,status);    /* Do the search          */
  183.     c = ectoc(expc = get_char());        /* Get another character      */
  184.     }
  185.  
  186.     /* Top of the per character loop */
  187.             
  188.     for (;;)                    /* ISearch per character loop */
  189.     {
  190.     /* Check for magic characters first: */
  191.     /* Most cases here change the search */
  192.  
  193.     if (expc == metac)            /* Want to quit searching?    */
  194.         return (TRUE);            /* Quit searching now          */
  195.  
  196.     switch (c)                /* dispatch on the input char */
  197.     {
  198.       case IS_ABORT:            /* If abort search request    */
  199.         return(FALSE);            /* Quit searching again          */
  200.  
  201.       case IS_REVERSE:            /* If backward search          */
  202.       case IS_FORWARD:            /* If forward search          */
  203.       case IS_VMSFORW:            /*  of either flavor          */
  204.         if (c == IS_REVERSE)        /* If reverse search          */
  205.         n = -1;                /* Set the reverse direction  */
  206.         else                /* Otherwise,               */
  207.         n = 1;                /*  go forward              */
  208.         status = scanmore(pat,n,TRUE);    /* Start the search again     */
  209.         c = ectoc(expc = get_char());        /* Get the next char          */
  210.         continue;                /* Go continue with the search*/
  211.  
  212.       case IS_NEWLINE:            /* Carriage return          */
  213.         c = '\n';                /* Make it a new line          */
  214.         break;                /* Make sure we use it          */
  215.  
  216.       case IS_QUOTE:            /* Quote character          */
  217.       case IS_VMSQUOTE:            /*  of either variety          */
  218.         c = ectoc(expc = get_char());        /* Get the next char          */
  219.  
  220.       case IS_TAB:                /* Generically allowed          */
  221.       case '\n':                /*  controlled characters     */
  222.         break;                /* Make sure we use it          */
  223.  
  224.       case IS_BACKSP:            /* If a backspace:            */
  225.       case IS_RUBOUT:            /*  or if a Rubout:          */
  226.         if (cmd_offset <= 1)        /* Anything to delete?          */
  227.         return (TRUE);            /* No, just exit          */
  228.         --cmd_offset;            /* Back up over the Rubout    */
  229.         cmd_buff[--cmd_offset] = '\0'; /* Yes, delete last char   */
  230.         curwp->w_dotp = curline;        /* Reset the line pointer     */
  231.         curwp->w_doto = curoff;        /*  and the offset          */
  232.         n = init_direction;            /* Reset the search direction */
  233.         strncpy (pat, pat_save, NPAT);    /* Restore the old search str */
  234.         cmd_reexecute = 0;        /* Start the whole mess over  */
  235.         goto start_over;            /* Let it take care of itself */
  236.  
  237.       /* Presumably a quasi-normal character comes here */
  238.  
  239.       default:                /* All other chars              */
  240.         if (c < ' ')            /* Is it printable?          */
  241.         {                    /* Nope.              */
  242.         reeat (c);            /* Re-eat the char          */
  243.         return (TRUE);            /* And return the last status */
  244.         }
  245.     }  /* Switch */
  246.  
  247.     /* I guess we got something to search for, so search for it          */
  248.  
  249.     pat[cpos++] = c;            /* put the char in the buffer */
  250.     if (cpos >= NPAT)            /* too many chars in string?  */
  251.     {                    /* Yup.  Complain about it    */
  252.         mlwrite("? Search string too long");
  253.         return(TRUE);            /* Return an error          */
  254.     }
  255.     pat[cpos] = 0;                /* null terminate the buffer  */
  256.     col = echochar(c,col);            /* Echo the character          */
  257.     if (!status) {                /* If we lost last time          */
  258.         (*term.t_putchar)(BELL);        /* Feep again              */
  259.         (*term.t_flush)();            /* see that the feep feeps    */
  260.     } else                    /* Otherwise, we must have won*/
  261.         if (!(status = checknext(c,pat,n,status))) /* See if match          */
  262.         status = scanmore(pat,n,TRUE);    /*  or find the next match    */
  263.     c = ectoc(expc = get_char());        /* Get the next char          */
  264.     } /* for {;;} */
  265. }
  266.  
  267. /*
  268.  * Trivial routine to insure that the next character in the search string is
  269.  * still true to whatever we're pointing to in the buffer.  This routine will
  270.  * not attempt to move the "point" if the match fails, although it will 
  271.  * implicitly move the "point" if we're forward searching, and find a match,
  272.  * since that's the way forward isearch works.
  273.  *
  274.  * If the compare fails, we return FALSE and assume the caller will call
  275.  * scanmore or something.
  276.  */
  277.  
  278. int checknext (chr, patrn, dir, sts)/* Check next character in search string */
  279. char    chr;            /* Next char to look for         */
  280. char    *patrn;            /* The entire search string (incl chr)   */
  281. int    dir;            /* Search direction             */
  282. int    sts;            /* Search status             */
  283. {
  284.     register LINE *curline;        /* current line during scan          */
  285.     register int curoff;        /* position within current line          */
  286.     register int buffchar;        /* character at current position      */
  287.     int status;                /* how well things go              */
  288.  
  289.     if (!sts) return(FALSE);        /* Don't try unless ok so far          */
  290.  
  291.     /* setup the local scan pointer to current "." */
  292.  
  293.     curline = curwp->w_dotp;        /* Get the current line structure     */
  294.     curoff  = curwp->w_doto;        /* Get the offset within that line    */
  295.  
  296.     if (dir > 0)            /* If searching forward              */
  297.     {
  298.         if (curoff == llength(curline)) /* If at end of line              */
  299.         {
  300.         curline = lforw(curline);    /* Skip to the next line          */
  301.         if (curline == curbp->b_linep)
  302.         return (FALSE);        /* Abort if at end of buffer          */
  303.         curoff = 0;            /* Start at the beginning of the line */
  304.         buffchar = '\n';        /* And say the next char is NL          */
  305.     } else
  306.         buffchar = lgetc(curline, curoff++); /* Get the next char          */
  307.     if (status = eq(buffchar, chr))    /* Is it what we're looking for?      */
  308.     {
  309.         curwp->w_dotp = curline;    /* Yes, set the buffer's point          */
  310.         curwp->w_doto = curoff;    /*  to the matched character          */
  311.         curwp->w_flag |= WFMOVE;    /* Say that we've moved              */
  312.     }
  313.     return (status);        /* And return the status          */
  314.     } else                /* Else, if reverse search:          */
  315.     return (match_pat (patrn));    /* See if we're in the right place    */
  316. }
  317.  
  318. /*
  319.  * This hack will search for the next occurrence of <pat> in the buffer, either
  320.  * forward or backward.  It is called with the status of the prior search
  321.  * attempt, so that it knows not to bother if it didn't work last time.  If
  322.  * we can't find any more matches, "point" is left where it was before.  If
  323.  * we do find a match, "point" will be at the end of the matched string for
  324.  * forward searches and at the beginning of the matched string for reverse
  325.  * searches.
  326.  */
  327.  
  328. int scanmore(patrn,dir,sts)    /* search forward or back for a pattern          */
  329. char    *patrn;            /* string to scan for                  */
  330. int    dir;            /* direction to search                  */
  331. int    sts;            /* previous search status              */
  332. {
  333.     if (sts)             /* don't try unless successful last time      */
  334.     {
  335.         if (dir < 0)                /* reverse search?          */
  336.         sts = bakscan(patrn);        /* Yes, call our hacky routine*/
  337.     else
  338.         sts = forscan(patrn,PTEND);        /* Nope. Go forward          */
  339.     }
  340.     if (!sts) {
  341.         (*term.t_putchar)(BELL);        /* Feep if search fails       */
  342.     (*term.t_flush)();            /* see that the feep feeps    */
  343.     }
  344.     return(sts);                /* else, don't even try          */
  345. }
  346.  
  347. /*
  348.  * The following is a minimal implementation of the reverse of "forscan".
  349.  * We aren't using the routine in SEARCH.C because it likes to type stuff,
  350.  * but the real solution is probably to fix that instead of duplicate the
  351.  * code here like we're doing.  On the other hand, we don't want to touch
  352.  * more modules than we have to for this first round ...
  353.  *
  354.  * This always leaves "." at the beginning of the matched pattern string
  355.  */
  356.  
  357. int bakscan (patrn)        /* Scan backwards for a match              */
  358. char    *patrn;            /* Search string to be matched              */
  359. {
  360.     LINE *initline;            /* initial line pointer before scan   */
  361.     int initoff;            /* position within initial line          */
  362.  
  363.     /* Remember "point" on entry: */
  364.  
  365.     initline = curwp->w_dotp;        /* Get the current line structure     */
  366.     initoff  = curwp->w_doto;        /* Get the offset within that line    */
  367.  
  368.     /*
  369.      * Loop here, stepping the cursor until we match or until we reach the top
  370.      * of the buffer
  371.      */
  372.  
  373.     while (backchar(TRUE, 1))            /* As long as there're chars  */
  374.     if (match_pat (patrn))            /* See if we match          */
  375.         return (TRUE);            /* Yep.  Stop'er right here   */
  376.     curwp->w_dotp = initline;            /* Top of buffer, just reset  */
  377.     curwp->w_doto = initoff;            /*   to original "point"      */
  378.     curwp->w_flag |= WFMOVE;            /* In case backchar moved us  */
  379.     return (FALSE);                /* And return failure          */
  380. }
  381.  
  382. /*
  383.  * The following is a worker subroutine used by the reverse search.  It
  384.  * compares the pattern string with the characters at "." for equality. If
  385.  * any characters mismatch, it will return FALSE.
  386.  *
  387.  * This isn't used for forward searches, because forward searches leave "."
  388.  * at the end of the search string (instead of in front), so all that needs to
  389.  * be done is match the last char input.
  390.  */
  391.  
  392. int match_pat (patrn)    /* See if the pattern string matches string at "."   */
  393. char    *patrn;        /* String to match to buffer                 */
  394. {
  395.     register int  i;            /* Generic loop index/offset          */
  396.     register int buffchar;        /* character at current position      */
  397.     register LINE *curline;        /* current line during scan          */
  398.     register int curoff;        /* position within current line          */
  399.  
  400.     /* setup the local scan pointer to current "." */
  401.  
  402.     curline = curwp->w_dotp;        /* Get the current line structure     */
  403.     curoff  = curwp->w_doto;        /* Get the offset within that line    */
  404.  
  405.     /* top of per character compare loop: */
  406.  
  407.     for (i = 0; i < strlen(patrn); i++)    /* Loop for all characters in patrn   */
  408.     {
  409.         if (curoff == llength(curline)) /* If at end of line              */
  410.         {
  411.         curline = lforw(curline);    /* Skip to the next line          */
  412.         curoff = 0;            /* Start at the beginning of the line */
  413.         if (curline == curbp->b_linep)
  414.         return (FALSE);        /* Abort if at end of buffer          */
  415.         buffchar = '\n';        /* And say the next char is NL          */
  416.     } else
  417.         buffchar = lgetc(curline, curoff++); /* Get the next char          */
  418.     if (!eq(buffchar, patrn[i]))    /* Is it what we're looking for?      */
  419.         return (FALSE);        /* Nope, just punt it then          */
  420.     }
  421.     return (TRUE);            /* Everything matched? Let's celebrate*/
  422. }
  423.  
  424. /* Routine to prompt for I-Search string. */
  425.  
  426. int promptpattern(prompt)
  427. char *prompt;
  428. {
  429.     char tpat[NPAT+20];
  430.  
  431.     strcpy(tpat, prompt);        /* copy prompt to output string */
  432.     strcat(tpat, " [");            /* build new prompt string */
  433.     expandp(pat, &tpat[strlen(tpat)], NPAT/2);    /* add old pattern */
  434.     strcat(tpat, "]<META>: ");
  435.  
  436.     /* check to see if we are executing a command line */
  437.     if (!clexec) {
  438.     mlwrite(tpat);
  439.     }
  440.     return(strlen(tpat));
  441. }
  442.  
  443. /* routine to echo i-search characters */
  444.  
  445. int echochar(c,col)
  446. int    c;    /* character to be echoed */
  447. int    col;    /* column to be echoed in */
  448. {
  449.     movecursor(term.t_nrow,col);        /* Position the cursor          */
  450.     if ((c < ' ') || (c == 0x7F))        /* Control character?          */
  451.     {
  452.     switch (c)                /* Yes, dispatch special cases*/
  453.     {
  454.       case '\n':                /* Newline              */
  455.         (*term.t_putchar)('<');
  456.         (*term.t_putchar)('N');
  457.         (*term.t_putchar)('L');
  458.         (*term.t_putchar)('>');
  459.         col += 3;
  460.         break;
  461.  
  462.       case '\t':                /* Tab                  */
  463.         (*term.t_putchar)('<');
  464.         (*term.t_putchar)('T');
  465.         (*term.t_putchar)('A');
  466.         (*term.t_putchar)('B');
  467.         (*term.t_putchar)('>');
  468.         col += 4;
  469.         break;
  470.  
  471.       case 0x7F:                /* Rubout:              */
  472.         (*term.t_putchar)('^');        /* Output a funny looking     */
  473.         (*term.t_putchar)('?');        /*  indication of Rubout      */
  474.         col++;                /* Count the extra char       */
  475.         break;
  476.  
  477.       default:                /* Vanilla control char       */
  478.         (*term.t_putchar)('^');        /* Yes, output prefix          */
  479.             (*term.t_putchar)(c+0x40);        /* Make it "^X"              */
  480.         col++;                /* Count this char          */
  481.     }
  482.     } else
  483.     (*term.t_putchar)(c);            /* Otherwise, output raw char */
  484.     (*term.t_flush)();                /* Flush the output          */
  485.     return(++col);                /* return the new column no   */
  486. }
  487.  
  488. /*
  489.  * Routine to get the next character from the input stream.  If we're reading
  490.  * from the real terminal, force a screen update before we get the char. 
  491.  * Otherwise, we must be re-executing the command string, so just return the
  492.  * next character.
  493.  */
  494.  
  495. int get_char ()
  496. {
  497.     int    c;                /* A place to get a character          */
  498.  
  499.     /* See if we're re-executing: */
  500.  
  501.     if (cmd_reexecute >= 0)        /* Is there an offset?              */
  502.     if ((c = cmd_buff[cmd_reexecute++]) != 0)
  503.         return (c);            /* Yes, return any character          */
  504.  
  505.     /* We're not re-executing (or aren't any more).  Try for a real char      */
  506.  
  507.     cmd_reexecute = -1;        /* Say we're in real mode again          */
  508.     update(FALSE);            /* Pretty up the screen              */
  509.     if (cmd_offset >= CMDBUFLEN-1)    /* If we're getting too big ...          */
  510.     {
  511.     mlwrite ("? command too long");    /* Complain loudly and bitterly          */
  512.     return (metac);            /* And force a quit              */
  513.     }
  514.     c = get1key();        /* Get the next character          */
  515.     cmd_buff[cmd_offset++] = c; /* Save the char for next time        */
  516.     cmd_buff[cmd_offset] = '\0';/* And terminate the buffer          */
  517.     return (c);                /* Return the character              */
  518. }
  519.  
  520. /*
  521.  * Hacky routine to re-eat a character.  This will save the character to be
  522.  * re-eaten by redirecting the input call to a routine here.  Hack, etc.
  523.  */
  524.  
  525. /* Come here on the next term.t_getchar call: */
  526.  
  527. int uneat()
  528. {
  529.     int c;
  530.  
  531.     term.t_getchar = saved_get_char;    /* restore the routine address          */
  532.     c = eaten_char;            /* Get the re-eaten char          */
  533.     eaten_char = -1;            /* Clear the old char              */
  534.     return(c);                /* and return the last char          */
  535. }
  536.  
  537. int reeat(c)
  538. int    c;
  539. {
  540.     if (eaten_char != -1)        /* If we've already been here          */
  541.     return (NULL);            /* Don't do it again              */
  542.     eaten_char = c;            /* Else, save the char for later      */
  543.     saved_get_char = term.t_getchar;    /* Save the char get routine          */
  544.     term.t_getchar = uneat;        /* Replace it with ours              */
  545. }
  546.  
  547.